package com.xjd.edu.toolkit.helper;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * 冷热库路由器
 *
 * @author wangyu
 * @since 2022/7/19
 */
@Slf4j
public class HotColdRouter<T> {

    private final String table;

    private final T hot;
    private final T cold;

    /**
     * 主键路由阈值
     */
    private Integer threshold;

    public HotColdRouter(String table, T hot, T cold, Function<LocalDate, Integer> thresholdHandler) {
        this.table = table;
        this.hot = hot;
        this.cold = cold;
        this.init(thresholdHandler);
    }

    private void init(Function<LocalDate, Integer> supplier) {
        if (supplier == null) {
            return;
        }
        // 由于数据1个月才会迁移一次，所以理论上一个月检查一次即可，这里设置为5天检查一次（容错）
        Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(() -> {
            try {
                // 传入当前月份倒推6个月的1号
                final Integer v = supplier.apply(LocalDate.now().withDayOfMonth(1).minusMonths(6));
                if (v != null) {
                    this.threshold = v;
                    log.info("[{}]更新主键阈值为 {}", this.table, this.threshold);
                } else {
                    log.warn("[{}]查询主键阈值为空，当前阈值为 {}", this.table, this.threshold);
                }
            } catch (Exception e) {
                log.error("[{}]更新主键阈值出错，当前阈值为 {}", this.table, this.threshold, e);
            }
        }, 0, 5, TimeUnit.DAYS);
    }

    /**
     * 按主键路由
     *
     * @param key
     * @return
     */
    public T route(Integer key) {
        if (key == null || threshold == null || key >= threshold) {
            return hot;
        }
        log.warn("Route key {} -> cold", key);
        return cold;
    }

    /**
     * 主键列表分组
     *
     * @param keys
     * @return
     */
    public Map<T, List<Integer>> group(Collection<Integer> keys) {
        if (CollUtil.isEmpty(keys)) {
            return Collections.emptyMap();
        }
        final Map<T, List<Integer>> map = new HashMap<>(4);
        for (Integer key : keys) {
            if (key == null) {
                continue;
            }
            final T t = route(key);
            if (map.containsKey(t)) {
                map.get(t).add(key);
            } else {
                map.put(t, CollUtil.newArrayList(key));
            }
        }
        return map;
    }

    /**
     * 按日期路由
     *
     * @param key
     * @return
     */
    public T route(Date key) {
        // 如果路由条件为日期类型且为近6个月内则走热库，否则走冷库
        if (key == null || GameDateTime.of(key).isAfterOrEquals(GameDateTime.of(LocalDate.now().withDayOfMonth(1).plusMonths(-6), LocalTime.MIN))) {
            return hot;
        }
        log.warn("Route key {} -> cold", key);
        return cold;
    }

    /**
     * 按订单号路由，需要按类型解析订单号
     *
     * @param key
     * @return
     */
    public T route(String key) {
        if (StrUtil.isBlank(key)) {
            return hot;
        }
        // 不同表的订单号规则未必相同，所以按表进行调整
        switch (this.table) {
            case "t_room_order":
                // V_22053198179567
            case "t_virtual_product_order":
                // V_220101349020 | V_TEST2205071218314 订单号规则为去掉非数字前缀后前6个数字表示年月日（220101）
                return route(parseDateWithDefaultOrderNo(key));
            default:
                return hot;
        }
    }

    /**
     * 对订单号分组
     *
     * @param keys
     * @return
     */
    public Map<T, List<String>> groupWithOrderNos(Collection<String> keys) {
        if (CollUtil.isEmpty(keys)) {
            return Collections.emptyMap();
        }
        final Map<T, List<String>> map = new HashMap<>(4);
        for (String key : keys) {
            if (key == null) {
                continue;
            }
            final T t = route(key);
            if (map.containsKey(t)) {
                map.get(t).add(key);
            } else {
                map.put(t, CollUtil.newArrayList(key));
            }
        }
        return map;
    }

    /**
     * 从 t_virtual_product_order | t_room_order 表 orderNo 中提取日期部分
     *
     * @param orderNo
     * @return
     */
    static Date parseDateWithDefaultOrderNo(String orderNo) {
        final int[] chars = orderNo.chars()
                .filter(i -> i >= '0' && i <= '9')
                .limit(6)
                .toArray();
        // 如果订单号不符合规则，返回当前日期，应用默认路由策略
        if (chars.length != 6) {
            return new Date();
        }
        String first = new String(chars, 0, 2);
        String second = new String(chars, 2, 2);
        String third = new String(chars, 4, 2);
        // 处理年份是完整情况的路由
        if (Integer.parseInt(second) > 12 && first.equals("20")) {
            // 月份是一个大于12的数字，表示前四位才是年份 [2018, 2019, 2020, 2021, 2022]，将20部分去除
            first = second;
            second = third;
            third = "01";
        }

        // 合法则返回路由日期
        try {
            return GameDateTime.of(String.format("20%s-%s-%s 00:00:00", first, second, third)).toJdkDate();
        } catch (Exception e) {
            // 如果日期解析失败，直接返回当前时间，底层逻辑是大部分查询是查近期数据，不需要进行路由
            return GameDateTime.now().toJdkDate();
        }
    }

}
